from logging import log
import requests
from bs4 import BeautifulSoup
import re
import time
import threading
import os
import sys
import json
import logging
from logging.handlers import RotatingFileHandler
headers = {  # 新增全局headers定义
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'}
 
def fetch_search_results(url):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'}
    response = requests.get(url, headers=headers)
    response.raise_for_status()  # 检查请求是否成功
    return response.text
 
def extract_ips(html_content):
    print("正在解析的HTML内容：\n", html_content[:1000])  # 打印前1000个字符避免刷屏
    soup = BeautifulSoup(html_content, 'html.parser')
    ips = set()
    # 首次解析原始内容
    for text in soup.stripped_strings:
         if 'IP' in text:
            # 使用更精确的正则表达式提取IP
            ip_match = re.search(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', text)
            if ip_match:
                ips.add(ip_match.group(1))
                break  # 找到目标IP后立即停止搜索
    
    # 如果没有找到IP且存在iframe
    if not ips and soup.find('iframe'):
        for iframe in soup.find_all('iframe'):
            src = iframe.get('src')
            if src and src.startswith(('http://', 'https://')):
                try:
                    # 爬取iframe内容
                    iframe_response = requests.get(src, headers=headers)
                    iframe_content = iframe_response.text
                    # 递归解析iframe内容
                    ips.update(extract_ips(iframe_content))
                except Exception as e:
                    print(f"无法爬取iframe内容: {src}, 错误: {e}")
    
    return ips

def getAllIps(check_urls):
    # 新增统一处理方法
    def process_single_url(url):
        try:
            html_content = fetch_search_results(url)
            return extract_ips(html_content)
        except Exception as e:
            print(f"处理URL {url} 时出错: {e}")
            return set()
    
    # 处理所有URL并合并结果
    all_ips = set()
    for url in check_urls:
        current_ips = process_single_url(url)
        if current_ips:
            all_ips.update(current_ips)
            break  # 找到有效IP后立即终止循环

    return all_ips
def main():
    # 获取exe所在目录路径
    if getattr(sys, 'frozen', False):
        exe_dir = os.path.dirname(sys.executable)
    else:
        exe_dir = os.path.dirname(os.path.abspath(__file__))
    # 初始化日志配置
    log_dir = os.path.join(exe_dir, 'logs')
    os.makedirs(log_dir, exist_ok=True)
    
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)
    
    # 创建文件处理器（每天一个日志文件，保留7天）
    file_handler = logging.handlers.TimedRotatingFileHandler(
        filename=os.path.join(log_dir, 'app.log'),
        when='midnight',
        backupCount=7,
        encoding='utf-8'
    )
    
    # 创建控制台处理器
    console_handler = logging.StreamHandler()
    
    # 创建格式化器
    formatter = logging.Formatter(
        '%(asctime)s [%(levelname)s] %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S'
    )
    
    # 添加处理器
    file_handler.setFormatter(formatter)
    console_handler.setFormatter(formatter)
    logger.addHandler(file_handler)
    logger.addHandler(console_handler)
    
    config_path = os.path.join(exe_dir, 'config.json')
    # 读取配置文件
    try:
        with open(config_path , 'r', encoding='utf-8') as f:
            config = json.load(f)
         # 添加参数校验
        required_params = ['deviceId', 'check_interval', 'heartbeat_interval', 'check_urls']
        for param in required_params:
            if param not in config:
                raise KeyError(f"缺少必要参数: {param}")
        deviceId = config['deviceId']
        # 在配置读取处添加
        try:
            check_interval = int(config['check_interval'])
        except (ValueError, TypeError):
            logging.error("配置参数check_interval类型错误，已使用默认值60")
            check_interval = 60  # 设置默认值
        heartbeat_interval = int(config['heartbeat_interval'])  # 转换为整数
        check_urls = config['check_urls']
        heartbeat_url = config.get('heartbeat_url', 'http://localhost:1888/api/yt-business/ipRegion/heartbeat')  # 添加默认值
        ip_region_url = config.get('ip_region_url', 'https://qifu-api.baidubce.com/ip/geo/v1/district') 
        logging.info(
            f"配置文件读取成功:\n"
            f"【设备ID】deviceId={deviceId}\n"
            f"【IP检查间隔，秒】check_interval={check_interval}\n"
            f"【IP检查地址列表】check_urls={check_urls}\n"
            f"【心跳间隔，秒】heartbeat_interval={heartbeat_interval}\n"
            f"【心跳访问地址】heartbeat_url={heartbeat_url}\n"
            f"【IP地理区域地址】ip_region_url={ip_region_url}")
    except Exception as e:
        logging.error(f"配置文件读取失败: {e}")
        return
    ip = "222"  # 替换为实际的IP地址
    region = "222"  # 替换为实际的region
    # 新增缓存变量
    last_ip = None
    last_region = None
    def send_heartbeat():
        while True:
            try:
                params = {
                        "deviceId": deviceId,
                        "clientIp": ip,
                        "region": region
                    }
                logging.info(f"心跳发送成功 参数：{params}")

                # 这里需要替换实际参数值
                response = requests.get(
                    heartbeat_url,
                    params=params,
                    timeout=5  # 添加超时设置
                )
                response.raise_for_status()  # 检查HTTP状态码
            except requests.exceptions.ConnectionError:
                logging.error(f"[{time.strftime('%H:%M:%S')}] 无法连接心跳服务器")
            except requests.exceptions.Timeout:
                logging.error(f"[{time.strftime('%H:%M:%S')}] 心跳请求超时")
            except requests.exceptions.HTTPError as e:
                logging.error(f"[{time.strftime('%H:%M:%S')}] 服务器返回错误状态码：{e.response.status_code}")
            except Exception as e:
                logging.error(f"[{time.strftime('%H:%M:%S')}] 未知错误：{str(e)}")
            except Exception as e:
                logging.error(f"心跳发送失败: {e}")
            time.sleep(heartbeat_interval)
            # 启动心跳线程
    heartbeat_thread = threading.Thread(target=send_heartbeat, daemon=True)
    heartbeat_thread.start()
    logging.info("心跳线程已启动")
    while True:
        try:
            start_time = time.time()
            logging.info(f"\n开始执行IP检查 ({time.strftime('%Y-%m-%d %H:%M:%S')})")
            # 获取IP地址 并根据IP解析出县一级的位置信息 --- begin
            all_ips = getAllIps(check_urls)
            if not all_ips:
                logging.info("未找到有效IP")
                time.sleep(check_interval - (time.time() - start_time))
                continue
            logging.info("找到的IP地址：" + str(all_ips))
            # 新增IP地理位置查询
            logging.info("IP地理位置查询结果：")
            # 修改后
            ip = next(iter(all_ips)) if all_ips else '未知IP'
            if ip == '未知IP':
                logging.info("未获取到有效IP地址")
                time.sleep(check_interval - (time.time() - start_time))
                continue
             # 新增IP变化检查
            if ip == last_ip and last_region and ip != '未知IP':
                logging.info(f"IP{ip}未变化，使用缓存区域信息{last_region}")
                region = last_region
            else:
                # 执行地理查询
            # IP地理查询部分添加异常捕获
                try:
                    response = requests.get(f"{ip_region_url}?ip={ip}")
                    if response.status_code == 200:
                        print(response.json())
                        continent = response.json().get('data', {}).get('continent', '未知')
                        country = response.json().get('data', {}).get('country', '未知')
                        prov = response.json().get('data', {}).get('prov', '未知')
                        city = response.json().get('data', {}).get('city', '未知')
                        district = response.json().get('data', {}).get('district', '未知')
                        isp = response.json().get('data', {}).get('isp', '未知')
                        region = f"{continent}|{country}|{prov}|{city}|{district}|{isp}"
                        last_region = region  # 缓存最新区域信息
                        last_ip = ip         # 缓存最新IP
                        logging.info(f"{ip}: {continent}|{country}|{prov}|{city}|{district}|{isp}")
                    else:   
                        logging.info(f"{ip}: 无法获取地理位置")
                except requests.exceptions.RequestException as e:
                    logging.error(f"IP地理查询请求失败: {e}")
                except Exception as e:
                    logging.error(f"IP地理查询解析失败: {e}")
            # 获取IP地址 并根据IP解析出县一级的位置信息 --- end
            logging.info(f"本轮检查完成，下次检查将在{check_interval}秒后")
            elapsed = time.time() - start_time
            # 添加类型转换确保数值计算安全
            sleep_time = max(0, float(check_interval) - elapsed)  # 转换为浮点数
            logging.info(f"本轮检查完成，下次检查将在{sleep_time:.2f}秒后")
            time.sleep(sleep_time)
        except KeyboardInterrupt:
            logging.error("\n已手动停止定时检查")
            break
        except Exception as e:
            logging.error(f"主循环异常: {str(e)}")
if __name__ == '__main__':
    main()